﻿using System;
using System.Collections.Generic;
using System.Linq;
using Loki.Utils;

namespace Loki.Commands
{
    /// <summary>
    /// Loki default command type.
    /// </summary>
    internal class LokiRoutedCommand : LoggableObject, ICommand
    {
        public LokiRoutedCommand(bool P_IsDeepCommand)
            : this(P_IsDeepCommand, Guid.NewGuid().ToString())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LokiRoutedCommand"/> class.
        /// </summary>
        public LokiRoutedCommand()
            : this(Guid.NewGuid().ToString())
        {
        }

        public LokiRoutedCommand(string P_TagName)
            : this(false, P_TagName)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LokiRoutedCommand"/> class.
        /// </summary>
        /// <param name="P_IsBackgroundCommand">If set to <c>true</c> mark the command as deep. <c>false</c> for an UI command.</param>
        /// <param name="P_TagName">Name of the command.</param>
        public LokiRoutedCommand(bool P_IsBackgroundCommand, string P_TagName)
        {
            Tag = P_TagName;
            BackgroundCommand = P_IsBackgroundCommand;
            _LastCanExecute = null;
        }

        /// <summary>
        /// Gets a value indicating whether the command is a deep command.
        /// </summary>
        /// <value>
        ///   Is <c>True</c> if the command is a deep command; otherwise, <c>false</c>.
        /// </value>
        public bool BackgroundCommand { get; private set; }

        /// <summary>
        /// Gets the command name.
        /// </summary>
        public string Tag { get; private set; }

        /// <summary>
        /// Définit la méthode qui détermine si la commande peut s'exécuter dans son état actuel.
        /// </summary>
        /// <param name="P_Parameter">Données utilisées par la commande.Si la commande ne requiert pas que les données soient passées, cet objet peut avoir la valeur null.</param>
        /// <returns>
        /// true si cette commande peut être exécutée ; sinon false.
        /// </returns>
        public bool CanExecute(object P_Parameter)
        {
            // gets command handlers
            IEnumerable<ICommandHandler> L_Handlers = LokiServices.Command.GetHandlers(this);

            // check handlers presence
            if (L_Handlers == null)
            {
                UpdateCanExecute(false);
                return false;
            }

            // define parameter
            CanExecuteCommandEventArgs L_Args = new CanExecuteCommandEventArgs(this, P_Parameter);

            foreach (ICommandHandler L_Handler in L_Handlers)
            {
                if (L_Handler.State != null)
                {
                    bool L_Result = (BackgroundCommand && L_Handler.State.IsInitialized) || (!BackgroundCommand && L_Handler.State.IsActive);

                    // break if can not execute
                    if (!L_Result)
                    {
                        continue;
                    }
                }

                // check execution
                L_Handler.CanExecute(this, L_Args);

                // break if found
                if (L_Args.CanExecute)
                {
                    break;
                }
            }

            UpdateCanExecute(L_Args.CanExecute);

            return L_Args.CanExecute;
        }

        protected void UpdateCanExecute(bool P_Value)
        {
            Log.DebugFormat("{0} can execute = {1}", Tag, P_Value);
            // command change
            if (!_LastCanExecute.HasValue || _LastCanExecute.Value != P_Value)
            {
                _LastCanExecute = P_Value;
                OnCanExecuteChanged(EventArgs.Empty);
            }
        }

        private bool? _LastCanExecute;

        public void RefreshState()
        {
            OnCanExecuteChanged(EventArgs.Empty);
        }

        #region CanExecuteChanged
        public event EventHandler CanExecuteChanged;

        protected virtual void OnCanExecuteChanged(EventArgs e)
        {
            EventHandler L_Handler = CanExecuteChanged;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion

        /// <summary>
        /// Définit la méthode à appeler lorsque la commande est appelée.
        /// </summary>
        /// <param name="P_Parameter">Données utilisées par la commande. Si la commande ne requiert pas que les données soient passées, cet objet peut avoir la valeur null.</param>
        public void Execute(object P_Parameter)
        {
            // gets command handlers
            IEnumerable<ICommandHandler> L_Handlers = LokiServices.Command.GetHandlers(this);

            // check handlers presence
            if (L_Handlers == null)
            {
                return;
            }
            else
            {
                L_Handlers = L_Handlers.ToList();
            }

            // define parameter
            CanExecuteCommandEventArgs L_Args = new CanExecuteCommandEventArgs(this, P_Parameter);

            foreach (ICommandHandler L_Handler in L_Handlers)
            {
                L_Args.CanExecute = false;

                if (L_Handler.State != null)
                {
                    bool L_Result = (BackgroundCommand && L_Handler.State.IsInitialized) || (!BackgroundCommand && L_Handler.State.IsActive);

                    // break if can not execute
                    if (!L_Result)
                    {
                        continue;
                    }
                }

                // check execution
                L_Handler.CanExecute(this, L_Args);

                if (L_Args.CanExecute && L_Handler.ConfirmDelegate != null)
                {
                    L_Args.CanExecute &= L_Handler.ConfirmDelegate(L_Args);
                }

                // break if found
                if (L_Args.CanExecute)
                {
                    L_Handler.Execute(this, L_Args);
                }
            }
        }
    }
}
